// Flavor modified sound.c and sound.h from NEOPOP
//  which was originally based on sn76496.c from MAME
//  some ideas also taken from NeoPop-SDL code

//---------------------------------------------------------------------------
// Originally from
// NEOPOP : Emulator as in Dreamland
//
// Copyright (c) 2001-2002 by neopop_uk
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version. See also the license.txt file for
//	additional informations.
//---------------------------------------------------------------------------


/************************************************************************
 *                                                                      *
 *	Portions, but not all of this source file are based on MAME v0.60	*
 *	File "sn76496.c". All copyright goes to the original author.		*
 *	The remaining parts, including DAC processing, by neopop_uk			*
 *                                                                      *
 ************************************************************************/

//#ifndef __GP32__
//#include "stdafx.h"
//#endif

#include "neopopsound.h"


//#include "main.h"
#include "memory.h"
//#include "menu.h"

#include <cstring>

//=============================================================================

SoundChip toneChip;
SoundChip noiseChip;

bool mute = false;

//==== DAC
#define DAC_BUFFERSIZE		(2560 * 1024) //at (256 * 1024) the PC version will crash on MS2 intro

int dacLBufferRead, dacLBufferWrite, dacLBufferCount;
int dacRBufferRead, dacRBufferWrite, dacRBufferCount;
unsigned char dacBufferL[DAC_BUFFERSIZE];
unsigned char dacBufferR[DAC_BUFFERSIZE];

#ifndef __GP32__

int volume = 0;//SDL_MIX_MAXVOLUME;//SDL_MIX_MAXVOLUME / 2;

void increaseVolume()
{
    if(volume < 100)//SDL_MIX_MAXVOLUME)
        volume++;
}

void decreaseVolume()
{
    if(volume > 0)
        volume--;
}

#endif

//=============================================================================

#define SOUNDCHIPCLOCK	(3072000)	//Unverified / sounds correct

#define MAX_OUTPUT 0x7fff
#define STEP 0x10000		//Fixed point adjuster (this seems off)

#define MAX_OUTPUT_STEP 0x7fff0000
#define STEP_SHIFT 16

static unsigned int VolTable[16];
static unsigned int UpdateStep = 0;	//Number of steps during one sample.

/* Formulas for noise generator */
/* bit0 = output */

/* noise feedback for white noise mode (verified on real SN76489 by John Kortink) */
#define FB_WNOISE 0x14002	/* (16bits) bit16 = bit0(out) ^ bit2 ^ bit15 */

/* noise feedback for periodic noise mode */
#define FB_PNOISE 0x08000	/* 15bit rotate */

/* noise generator start preset (for periodic noise) */
#define NG_PRESET 0x0f35

#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)

//=============================================================================

static unsigned short sample_chip_tone(void)
{
	int i;
	int vol[4];
	unsigned int out;
	int left;

	/* vol[] keeps track of how long each square wave stays */
	/* in the 1 position during the sample period. */
	vol[0] = vol[1] = vol[2] = vol[3] = 0;

	for (i = 0; i < 3; i++)
	{
		if (toneChip.Output[i]) vol[i] += toneChip.Count[i];
		toneChip.Count[i] -= STEP;

		/* Period[i] is the half period of the square wave. Here, in each */
		/* loop I add Period[i] twice, so that at the end of the loop the */
		/* square wave is in the same status (0 or 1) it was at the start. */
		/* vol[i] is also incremented by Period[i], since the wave has been 1 */
		/* exactly half of the time, regardless of the initial position. */
		/* If we exit the loop in the middle, Output[i] has to be inverted */
		/* and vol[i] incremented only if the exit status of the square */
		/* wave is 1. */

		while (toneChip.Count[i] <= 0)
		{
			toneChip.Count[i] += toneChip.Period[i];
			if (toneChip.Count[i] > 0)
			{
				toneChip.Output[i] ^= 1;
				if (toneChip.Output[i]) vol[i] += toneChip.Period[i];
				break;
			}
			toneChip.Count[i] += toneChip.Period[i];
			vol[i] += toneChip.Period[i];
		}
		if (toneChip.Output[i]) vol[i] -= toneChip.Count[i];
	}

	left = STEP;
	do
	{
		int nextevent;

		if (toneChip.Count[3] < left) nextevent = toneChip.Count[3];
		else nextevent = left;

		if (toneChip.Output[3]) vol[3] += toneChip.Count[3];
		toneChip.Count[3] -= nextevent;
		if (toneChip.Count[3] <= 0)
		{
			if (toneChip.RNG & 1) toneChip.RNG ^= toneChip.NoiseFB;
			toneChip.RNG >>= 1;
			toneChip.Output[3] = toneChip.RNG & 1;
			toneChip.Count[3] += toneChip.Period[3];
			if (toneChip.Output[3]) vol[3] += toneChip.Period[3];
		}
		if (toneChip.Output[3]) vol[3] -= toneChip.Count[3];

		left -= nextevent;
	} while (left > 0);

	out = vol[0] * toneChip.Volume[0] + vol[1] * toneChip.Volume[1] +
		vol[2] * toneChip.Volume[2];

	//if (out > MAX_OUTPUT * STEP) out = MAX_OUTPUT * STEP;
	if (out > MAX_OUTPUT_STEP) out = MAX_OUTPUT_STEP;

	return out>>STEP_SHIFT;//out / STEP;
}

//=============================================================================

static unsigned short sample_chip_noise(void)
{
	int i;

	int vol[4];
	int vol3 = 0;
	unsigned int out;
	int left;

	/* vol[] keeps track of how long each square wave stays */
	/* in the 1 position during the sample period. */

	vol[0] = vol[1] = vol[2] = vol[3] = 0;
	for (i = 0; i < 3; i++)
	{
		if (noiseChip.Output[i]) vol[i] += noiseChip.Count[i];
		noiseChip.Count[i] -= STEP;

		// Period[i] is the half period of the square wave. Here, in each
		// loop I add Period[i] twice, so that at the end of the loop the
		// square wave is in the same status (0 or 1) it was at the start.
		// vol[i] is also incremented by Period[i], since the wave has been 1
		// exactly half of the time, regardless of the initial position.
		// If we exit the loop in the middle, Output[i] has to be inverted
		// and vol[i] incremented only if the exit status of the square
		// wave is 1.

		while (noiseChip.Count[i] <= 0)
		{
			noiseChip.Count[i] += noiseChip.Period[i];
			if (noiseChip.Count[i] > 0)
			{
				noiseChip.Output[i] ^= 1;
				if (noiseChip.Output[i]) vol[i] += noiseChip.Period[i];
				break;
			}
			noiseChip.Count[i] += noiseChip.Period[i];
			vol[i] += noiseChip.Period[i];
		}
		if (noiseChip.Output[i]) vol[i] -= noiseChip.Count[i];
	}

	left = STEP;
	do
	{
		int nextevent;

		if (noiseChip.Count[3] < left) nextevent = noiseChip.Count[3];
		else nextevent = left;

		if (noiseChip.Output[3]) vol3 += noiseChip.Count[3];
		noiseChip.Count[3] -= nextevent;
		if (noiseChip.Count[3] <= 0)
		{
			if (noiseChip.RNG & 1) noiseChip.RNG ^= noiseChip.NoiseFB;
			noiseChip.RNG >>= 1;
			noiseChip.Output[3] = noiseChip.RNG & 1;
			noiseChip.Count[3] += noiseChip.Period[3];
			if (noiseChip.Output[3]) vol3 += noiseChip.Period[3];
		}
		if (noiseChip.Output[3]) vol3 -= noiseChip.Count[3];

		left -= nextevent;
	} while (left > 0);
	out = vol3 * noiseChip.Volume[3];

	//if (out > MAX_OUTPUT * STEP) out = MAX_OUTPUT * STEP;
	if (out > MAX_OUTPUT_STEP) out = MAX_OUTPUT_STEP;

	return out>>STEP_SHIFT;//out / STEP;
}

//=============================================================================

void sound_update(unsigned short* chip_buffer, int length_bytes)
{
	while (length_bytes)
	{
		//Mix a mono track out of: (Tone + Noise) >> 1
		//Write it to the sound buffer
		*(chip_buffer++) = (sample_chip_tone() + sample_chip_noise()) >> 1;

		length_bytes-= 2; // 2 bytes = 16 bits
	}
}

//=============================================================================

void WriteSoundChip(SoundChip* chip, unsigned char data)
{
	//Command
	if (data & 0x80)
	{
		int r = (data & 0x70) >> 4;
		int c = r/2;

		chip->LastRegister = r;
		chip->Register[r] = (chip->Register[r] & 0x3f0) | (data & 0x0f);

		switch(r)
		{
		case 0:	/* tone 0 : frequency */
		case 2:	/* tone 1 : frequency */
		case 4:	/* tone 2 : frequency */
			chip->Period[c] = UpdateStep * chip->Register[r];
			if (chip->Period[c] == 0) chip->Period[c] = UpdateStep;
			if (r == 4)
			{
				/* update noise shift frequency */
				if ((chip->Register[6] & 0x03) == 0x03)
					chip->Period[3] = 2*chip->Period[2];
			}
			break;

		case 1:	/* tone 0 : volume */
		case 3:	/* tone 1 : volume */
		case 5:	/* tone 2 : volume */
		case 7:	/* noise  : volume */
			chip->Volume[c] = VolTable[data & 0xF];
			break;

		case 6:	/* noise  : frequency, mode */
			{
				int n = chip->Register[6];
				chip->NoiseFB = (n & 4) ? FB_WNOISE : FB_PNOISE;
				n &= 3;
				/* N/512,N/1024,N/2048,Tone #2 output */
				chip->Period[3] = (n == 3) ? 2 * chip->Period[2] : (UpdateStep << (5+n));

				/* reset noise shifter */
				chip->RNG = NG_PRESET;
				chip->Output[3] = chip->RNG & 1;

			}
			break;
		}
	}
	else
	{
		int r = chip->LastRegister;
		int c = r/2;

		switch (r)
		{
			case 0:	/* tone 0 : frequency */
			case 2:	/* tone 1 : frequency */
			case 4:	/* tone 2 : frequency */
				chip->Register[r] = (chip->Register[r] & 0x0f) | ((data & 0x3f) << 4);
				chip->Period[c] = UpdateStep * chip->Register[r];
				if (chip->Period[c] == 0) chip->Period[c] = UpdateStep;
				if (r == 4)
				{
					/* update noise shift frequency */
					if ((chip->Register[6] & 0x03) == 0x03)
						chip->Period[3] = 2 * chip->Period[2];
				}
				break;
		}
	}
}

//=============================================================================
void dac_writeL(unsigned char data)
{
    //Write to buffer
    dacBufferL[dacLBufferWrite++] = data;//(data-0x80)<<8;

    //dacLBufferWrite++;
    if (dacLBufferWrite == DAC_BUFFERSIZE)
        dacLBufferWrite = 0;

    //Overflow?
    dacLBufferCount++;
    if (dacLBufferCount == DAC_BUFFERSIZE)
    {
        //dbg_printf("dac_write: DAC buffer overflow\nPlease report this to the author.");
        dacLBufferCount = 0;
    }
}

void dac_writeR(unsigned char data)
{
    //Write to buffer
	dacBufferR[dacRBufferWrite++] = data;
	
	if (dacRBufferWrite == DAC_BUFFERSIZE)
		dacRBufferWrite = 0;

	//Overflow?
	dacRBufferCount++;
	if (dacRBufferCount == DAC_BUFFERSIZE)
	{
		//dbg_printf("dac_write: DAC buffer overflow\nPlease report this to the author.");
		dacRBufferCount = 0;
	}
}

void dac_mixer(unsigned short* stream, int length_bytes)
{
    int length_words = length_bytes>>1;
    length_bytes &= 0xFFFFFFFE; //make sure it's 16bit safe

    if(dacLBufferRead+length_words >= DAC_BUFFERSIZE)
    {
        dacLBufferRead = length_words-(DAC_BUFFERSIZE-dacLBufferRead);
    }
    else
    {
        dacLBufferRead += length_words;
    }

    dacLBufferCount -= length_words; //need it in 16bits

	if(dacRBufferRead+length_words >= DAC_BUFFERSIZE)
    {
        dacRBufferRead = length_words-(DAC_BUFFERSIZE-dacRBufferRead);
    }
    else
    {
        dacRBufferRead += length_words;
    }

    dacRBufferCount -= length_words; //need it in 16bits
}

void dac_update(unsigned char * dac_buffer, int length_bytes)
{
	while (length_bytes > 1)
	{
		//Copy then clear DAC data
		//*(dac_buffer++) = (dacBufferR[dacRBufferRead]<<8) | dacBufferL[dacLBufferRead];
		*(dac_buffer++) = dacBufferL[dacLBufferRead];
		//*(dac_buffer++) = dacBufferR[dacRBufferRead];

		dacBufferL[dacLBufferRead] = 0x80;  //silence?
		dacBufferR[dacRBufferRead] = 0x80;  //silence?

		length_bytes--;	// 1 byte = 8 bits

		if (dacLBufferCount > 0)
		{
			dacLBufferCount--;

			//Advance the DAC read
			dacLBufferRead++;
			if (++dacLBufferRead == DAC_BUFFERSIZE)
				dacLBufferRead = 0;
		}
		if (dacRBufferCount > 0)
		{
			dacRBufferCount--;

			//Advance the DAC read
			dacRBufferRead++;
			if (++dacRBufferRead == DAC_BUFFERSIZE)
				dacRBufferRead = 0;
		}
	}
}

//=============================================================================

//Resets the sound chips, also used whenever sound options are changed
void sound_init(int SampleRate)
{
	int i;
	double out;

	/* the base clock for the tone generators is the chip clock divided by 16; */
	/* for the noise generator, it is clock / 256. */
	/* Here we calculate the number of steps which happen during one sample */
	/* at the given sample rate. No. of events = sample rate / (clock/16). */
	/* STEP is a multiplier used to turn the fraction into a fixed point */
	/* number. */
	UpdateStep = (unsigned int)(((double)STEP * SampleRate * 16) / SOUNDCHIPCLOCK);

	//Initialise Left Chip
	memset(&toneChip, 0, sizeof(SoundChip));

	//Initialise Right Chip
	memset(&noiseChip, 0, sizeof(SoundChip));

   //Silence the chip and DAC
   memset(&blockSound, 0x80, CHIPBUFFERLENGTH);
   memset(&blockDAC, 0x80, DACBUFFERLENGTH);

	//Default register settings
	for (i = 0;i < 8;i+=2)
	{
		toneChip.Register[i] = 0;
		toneChip.Register[i + 1] = 0x0f;	/* volume = 0 */
		noiseChip.Register[i] = 0;
		noiseChip.Register[i + 1] = 0x0f;	/* volume = 0 */
	}

	for (i = 0;i < 4;i++)
	{
		toneChip.Output[i] = 0;
		toneChip.Period[i] = toneChip.Count[i] = UpdateStep;
		noiseChip.Output[i] = 0;
		noiseChip.Period[i] = noiseChip.Count[i] = UpdateStep;
	}

	//Build the volume table
	out = MAX_OUTPUT / 3;

	/* build volume table (2dB per step) */
	for (i = 0;i < 15;i++)
	{
		VolTable[i] = (unsigned int)out;
		out /= 1.258925412;	/* = 10 ^ (2/20) = 2dB */
	}
	VolTable[15] = 0;


	//Clear the DAC buffer
	for (i = 0; i < DAC_BUFFERSIZE; i++)
	{
		dacBufferL[i] = 0x80;
		dacBufferR[i] = 0x80;
	}

	dacLBufferCount = 0;
	dacLBufferRead = 0;
	dacLBufferWrite = 0;
		
	dacRBufferCount = 0;
	dacRBufferRead = 0;
	dacRBufferWrite = 0;
}

//=============================================================================
#define NGPC_CHIP_FREQUENCY		44100
int chip_freq=NGPC_CHIP_FREQUENCY;//what we'd prefer

// ====== Chip sound =========
int lastChipWrite = 0, chipWrite = UNDEFINED;	//Write Cursor

// ====== DAC sound =========
int lastDacWrite = 0, dacWrite = UNDEFINED;		//Write Cursor

unsigned char blockSound[CHIPBUFFERLENGTH], blockDAC[DACBUFFERLENGTH];		// Gets filled with sound data.
unsigned int blockSoundWritePtr = 0;
unsigned int blockSoundReadPtr = 0;

void system_sound_chipreset(void)
{
	//Initialises sound chips, matching frequncies
	sound_init(chip_freq);
}

void mixaudioCallback(void *userdata, unsigned char *stream, int len)
{
    sound_update((unsigned short*)blockSound, len);	//Get sound data
//    memcpy(stream, blockSound, len);        //put it in the buffer
    //SDL_MixAudio(stream, blockSound, len, volume); //mix it to the buffer

    if(dacLBufferCount >= len)
    {
        //dac_update((unsigned short*)blockDAC, len);	//Get DAC data
        //SDL_MixAudio(stream, blockDAC, len, volume); //mix it to the buffer

        dac_mixer((unsigned short*)stream, len);	//Get DAC data
    }
}

int sound_system_init()
{
   system_sound_chipreset();	//Resets chips

   return 1;
}


//call this every so often to update the sound output

void system_sound_update(void)
{
}




